package com.learn.http;

import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.collections4.MapUtils;

import javax.net.ssl.*;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author : Yu.Tan
 * @date : 2023/5/12 15:11
 * @desc : **
 */
@Slf4j
public class OkHttpUtil {

    public static final OkHttpClient CLIENT;

    private static SSLContext sslContext;
    private static X509TrustManager trustManager;

    public static HostnameVerifier hostnameVerifier() {
        return (hostname, session) -> true;
    }

    static {
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            trustManager = (X509TrustManager) trustManagers[0];

            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{trustManager}, null);
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static {
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .hostnameVerifier(hostnameVerifier())
                .sslSocketFactory(sslContext.getSocketFactory(), trustManager);
        CLIENT = builder.build();
    }

    public static InputStream download(String url) {
        return download(url, null);
    }

    public static InputStream download(String url, Map<String, String> headers) {
        Request.Builder builder = new Request.Builder()
                .url(url)
                .get();
        if (MapUtils.isNotEmpty(headers)) {
            builder.headers(Headers.of(headers));
        }
        Response response = execute(builder.build());
        ResponseBody body = response.body();
        return body.byteStream();
    }

    public static String get(String url) {
        return get(url, null, (response, content) -> content);
    }

    public static <T> T get(String url, Map<String, String> headers, Parser<T> parser) {
        Request.Builder builder = new Request.Builder().url(url).get();
        return execute(builder, headers, parser);
    }

    public static <T> T post(String url, String body, okhttp3.MediaType mediaType, Parser<T> parser) {
        return post(url, body, mediaType, null, parser);
    }

    public static <T> T post(String url, String body, okhttp3.MediaType mediaType, Map<String, String> headers, Parser<T> parser) {
        RequestBody requestBody = RequestBody.create(mediaType, body);
        Request.Builder builder = new Request.Builder().url(url).post(requestBody);
        return execute(builder, headers, parser);
    }

    public static <T> T execute(Request.Builder builder, Map<String, String> headers, Parser<T> parser) {
        if (headers != null && !headers.isEmpty()) {
            builder.headers(Headers.of(headers));
        }
        return execute(builder, parser);
    }

    public static <T> T execute(Request.Builder builder, Parser<T> parser) {
        Request request = builder.build();
        Response response = execute(request);
        try {
            String body = response.body().string();
            log.info("OKHttp send. url:{} | response:{}", request.url(), body);
            return parser.parse(response, body);
        } catch (Throwable e) {
            log.error("read response error", e);
        }
        return null;
    }

    public static ExecutorBuilder builder() {
        return new ExecutorBuilder();
    }

    public static Response execute(Request request) {
        try {
            Response response = CLIENT.newCall(request).execute();
            return response;
        } catch (Throwable e) {
            log.error("call remote error " + request.url(), e);
            throw new RuntimeException("call remote error", e);
        }
    }


    public static class ExecutorBuilder {
        private Request.Builder request;
        private Parser parser;

        public ExecutorBuilder() {
            this.request = new Request.Builder();
            this.parser = (response, content) -> content;
        }

        public ExecutorBuilder url(String url) {
            request.url(url);
            return this;
        }

        public ExecutorBuilder headers(Map<String, String> headers) {
            request.headers(Headers.of(headers));
            return this;
        }

        public ExecutorBuilder parser(Parser parser) {
            this.parser = parser;
            return this;
        }

        public <T> T post(okhttp3.MediaType mediaType, String body) {
            RequestBody requestBody = RequestBody.create(mediaType, body);
            request.post(requestBody);
            return (T) OkHttpUtil.execute(request, parser);
        }

        public <T> T get() {
            request.get();
            return (T) OkHttpUtil.execute(request, parser);
        }

        public <T> T delete() {
            request.delete();
            return (T) OkHttpUtil.execute(request, parser);
        }

        public <T> T delete(okhttp3.MediaType mediaType, String body) {
            RequestBody requestBody = RequestBody.create(mediaType, body);
            request.delete(requestBody);
            return (T) OkHttpUtil.execute(request, parser);
        }
    }

    public interface Parser<T> {
        T parse(Response response, String content);
    }

    public static class MediaType {
        public static okhttp3.MediaType JSON = okhttp3.MediaType.parse(org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE);
        public static okhttp3.MediaType FORM = okhttp3.MediaType.parse(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE);
        public static okhttp3.MediaType XML = okhttp3.MediaType.parse(org.springframework.http.MediaType.APPLICATION_XML_VALUE);
    }
}
